package hu.bme.mit.rifl.coordinator.impl;

import hu.bme.mit.rifl.business.preprocess.nodes.AcknowledgeOrder;
import hu.bme.mit.rifl.business.preprocess.nodes.CheckRepo;
import hu.bme.mit.rifl.business.preprocess.nodes.EnqueueOrder;
import hu.bme.mit.rifl.business.preprocess.nodes.SubmitOrder;
import hu.bme.mit.rifl.business.process.nodes.BookTransaction;
import hu.bme.mit.rifl.business.process.nodes.FulfillOrder;
import hu.bme.mit.rifl.business.process.nodes.RejectOrder;
import hu.bme.mit.rifl.business.process.nodes.ServeOrder;
import hu.bme.mit.rifl.coordinator.Coordiantor;
import hu.bme.mit.rifl.datamodel.Order;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.PriorityBlockingQueue;

import org.osgi.service.log.LogService;

public class CoordinatorImpl implements Coordiantor {

	private SubmitOrder submitOrder = null;
	private AcknowledgeOrder acknowledgeOrder = null;
	private EnqueueOrder enqueueOrder = null;
	private CheckRepo checkRepo = null;
	private RejectOrder rejectOrder = null;
	private ServeOrder serveOrder = null;
	private BookTransaction bookTransaction = null;
	private FulfillOrder fulfillOrder = null;

	private PriorityBlockingQueue<Order> new2submit;
	private PriorityBlockingQueue<Order> submit2ack;
	private PriorityBlockingQueue<Order> ack2enqueue;
	private PriorityBlockingQueue<Order> enqueue2check;
	private PriorityBlockingQueue<Order> check2reject;
	private PriorityBlockingQueue<Order> check2success;
	private PriorityBlockingQueue<Order> book2serve;
	private PriorityBlockingQueue<Order> serve2book;
	private PriorityBlockingQueue<Order> success2fulfill;
	private PriorityBlockingQueue<Order> finished;

	private LogService log = null;

	public CoordinatorImpl() {
		new2submit = new PriorityBlockingQueue<Order>();
		submit2ack = new PriorityBlockingQueue<Order>();
		ack2enqueue = new PriorityBlockingQueue<Order>();
		enqueue2check = new PriorityBlockingQueue<Order>();
		check2reject = new PriorityBlockingQueue<Order>();
		check2success = new PriorityBlockingQueue<Order>();
		book2serve = new PriorityBlockingQueue<Order>();
		serve2book = new PriorityBlockingQueue<Order>();
		success2fulfill = new PriorityBlockingQueue<Order>();
		finished = new PriorityBlockingQueue<Order>();
	}

	@Override
	public void newOrder(int id) {
		log(LogService.LOG_INFO, "New Order with id: " + id);
		Order order = new Order(id);
		new2submit.add(order);
	}

	@Override
	public void step(String nodeName) {
		switch (nodeName) {
		case "SubmitOrder":
			if (submitOrder != null) {
				Order order = new2submit.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is submitted.");
					submit2ack.add(submitOrder.process(order));
				} else {
					log(LogService.LOG_INFO, "There is no order to submit.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot submit any order, service is not available.");
			}
			break;
		case "AcknowledgeOrder":
			if (acknowledgeOrder != null) {
				Order order = submit2ack.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is acknowledged.");
					ack2enqueue.add(acknowledgeOrder.process(order));
				} else {
					log(LogService.LOG_INFO,
							"There is no order to acknowledge.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot acknowledge any order, service is not available.");
			}
			break;
		case "EnqueueOrder":
			if (enqueueOrder != null) {
				Order order = ack2enqueue.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is enqueued.");
					enqueue2check.add(enqueueOrder.process(order));
				} else {
					log(LogService.LOG_INFO, "There is no order to enqueue.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot enqueue any order, service is not available.");
			}
			break;
		case "CheckRepo":
			if (checkRepo != null) {
				Order order = enqueue2check.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is checked.");
					checkRepo.process(order);

					if (order.getState().equalsIgnoreCase("FEASIBLE")) {
						log(LogService.LOG_INFO, "Order is feasible.");
						check2success.add(order);
					} else {
						log(LogService.LOG_INFO, "Order is unfeasible.");
						check2reject.add(order);
					}

				} else {
					log(LogService.LOG_INFO, "There is no order to check.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot check any order, service is not available.");
			}
			break;
		case "RejectOrder":
			if (rejectOrder != null) {
				Order order = check2reject.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is rejected.");
					finished.add(rejectOrder.process(order));
				} else {
					log(LogService.LOG_INFO, "There is no order to reject.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot reject, service is not available.");
			}
			break;
		case "ServeOrder":
			if (serveOrder != null) {
				Order order = check2success.poll();

				if (order == null)
					order = book2serve.poll();

				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is served.");
					serveOrder.process(order);

					if (order.isBooked()) {
						success2fulfill.add(order);
					} else {
						serve2book.add(order);
					}

				} else {
					log(LogService.LOG_INFO, "There is no order to serve.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot serve any order, service is not available.");
			}
			break;
		case "BookTransaction":
			if (bookTransaction != null) {
				Order order = check2success.poll();

				if (order == null)
					order = serve2book.poll();

				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is booked.");
					bookTransaction.process(order);

					if (order.getState().equalsIgnoreCase("FEASIBLE")) {
						book2serve.add(order);
					} else {
						success2fulfill.add(order);
					}

				} else {
					log(LogService.LOG_INFO, "There is no order to book.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot book any order, service is not available.");
			}
			break;
		case "FulfillOrder":
			if (fulfillOrder != null) {
				Order order = success2fulfill.poll();
				if (order != null) {
					log(LogService.LOG_INFO, "Order " + order.getOrderID()
							+ " is fulfilled.");
					finished.add(fulfillOrder.process(order));
				} else {
					log(LogService.LOG_INFO, "There is no order to fulfill.");
				}
			} else {
				log(LogService.LOG_INFO,
						"Cannot fulfill any order, service is not available.");
			}
			break;
		default:
			log(LogService.LOG_WARNING, "Invalid argument: " + nodeName);
			break;
		}

	}

	@Override
	public void step(String nodeName, boolean decision) {
		switch (nodeName) {
		case "CheckRepo":
			if (checkRepo != null) {
				checkRepo.setDecision(decision);
			}
			step("CheckRepo");
			break;
		default:
			step(nodeName);
			break;
		}
	}

	@Override
	public void list() {
		System.out.println("--- Service availablility ---");
		System.out.println("SubmitOrder \t\t"
				+ (submitOrder == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("AcknowledgeOrder \t"
				+ (acknowledgeOrder == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("EnqueueOrder \t\t"
				+ (enqueueOrder == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("CheckRepo \t\t"
				+ (checkRepo == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("RejectOrder \t\t"
				+ (rejectOrder == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("ServeOrder \t\t"
				+ (serveOrder == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("BookTransaction \t"
				+ (bookTransaction == null ? "UNAVAILABLE" : "AVAILABLE"));
		System.out.println("FulfillOrder \t\t"
				+ (fulfillOrder == null ? "UNAVAILABLE" : "AVAILABLE"));

		System.out.println("--- Order status ---");
		ArrayList<Order> allOrder = new ArrayList<Order>();
		allOrder.addAll(Arrays.asList(new2submit.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(submit2ack.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(ack2enqueue.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(enqueue2check.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(check2reject.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(check2success.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(book2serve.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(serve2book.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(success2fulfill.toArray(new Order[0])));
		allOrder.addAll(Arrays.asList(finished.toArray(new Order[0])));

		for (Order order : allOrder) {
			System.out.println("Order " + order.getOrderID() + " is in state "
					+ order.getState() + " ["
					+ (order.isBooked() ? "booked" : "unbooked") + "]");
		}
	}

	@SuppressWarnings("unused")
	private void bind(SubmitOrder service) {
		log(LogService.LOG_INFO, "SubmitOrder registered.");
		submitOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(SubmitOrder service) {
		log(LogService.LOG_INFO, "SubmitOrder unregistered.");
		submitOrder = null;
	}

	@SuppressWarnings("unused")
	private void bind(AcknowledgeOrder service) {
		log(LogService.LOG_INFO, "AcknowledgeOrder registered.");
		acknowledgeOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(AcknowledgeOrder service) {
		log(LogService.LOG_INFO, "AcknowledgeOrder unregistered.");
		acknowledgeOrder = null;
	}

	@SuppressWarnings("unused")
	private void bind(EnqueueOrder service) {
		log(LogService.LOG_INFO, "EnqueueOrder registered.");
		enqueueOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(EnqueueOrder service) {
		log(LogService.LOG_INFO, "EnqueueOrder unregistered.");
		enqueueOrder = null;
	}

	@SuppressWarnings("unused")
	private void bind(CheckRepo service) {
		log(LogService.LOG_INFO, "CheckRepo registered.");
		checkRepo = service;
	}

	@SuppressWarnings("unused")
	private void unbind(CheckRepo service) {
		log(LogService.LOG_INFO, "CheckRepo unregistered.");
		checkRepo = null;
	}

	@SuppressWarnings("unused")
	private void bind(RejectOrder service) {
		log(LogService.LOG_INFO, "RejectOrder registered.");
		rejectOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(RejectOrder service) {
		log(LogService.LOG_INFO, "RejectOrder unregistered.");
		rejectOrder = null;
	}

	@SuppressWarnings("unused")
	private void bind(ServeOrder service) {
		log(LogService.LOG_INFO, "ServeOrder registered.");
		serveOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(ServeOrder service) {
		log(LogService.LOG_INFO, "ServeOrder unregistered.");
		serveOrder = null;
	}

	@SuppressWarnings("unused")
	private void bind(BookTransaction service) {
		log(LogService.LOG_INFO, "BookTransaction registered.");
		bookTransaction = service;
	}

	@SuppressWarnings("unused")
	private void unbind(BookTransaction service) {
		log(LogService.LOG_INFO, "BookTransaction unregistered.");
		bookTransaction = null;
	}

	@SuppressWarnings("unused")
	private void bind(FulfillOrder service) {
		log(LogService.LOG_INFO, "FulfillOrder registered.");
		fulfillOrder = service;
	}

	@SuppressWarnings("unused")
	private void unbind(FulfillOrder service) {
		log(LogService.LOG_INFO, "FulfillOrder unregistered.");
		fulfillOrder = null;
	}

	@SuppressWarnings("unused")
	private void bindLog(LogService log) {
		log(LogService.LOG_INFO, "LogService registered.");
		this.log = log;
	}

	@SuppressWarnings("unused")
	private void unbindLog(LogService log) {
		log(LogService.LOG_ERROR, "LogService unregistered.");
		this.log = null;
	}

	private void log(int logInfo, String string) {
		if (log != null) {
			// TODO: ne csak error-t logoljon
			// TODO: fájlba logoljon
			log.log(LogService.LOG_ERROR, string);
		}
	}
}
